{
	UCE - cross-platform game engine written in delphi.
	Copyright (c) 2009, Shutko Vitaly

	This file is part of UCE.

	UCE is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	UCE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
}

unit uFnt;

interface

uses
  uTyp, uMat, uFS, uGfx, uTex;

const
  //TFnt.Gen(lang)
  FNT_EN = 0;
  FNT_RU = 1;

type
  TFntSym = record
    x, y, w, h: Single;
    advance: Integer;
    coords: array[0..1] of TVec;
  end;

  TFnt = class(TListItem)
    fileName: String;
    tex: TTex;
    syms: array[0..255] of TFntSym;
    height, lineSkip: Integer;
    spacing, scale: Single;

    function Gen(name, fileName: String; height, weight: Integer; face: String; lang, texW, texH: Integer; smooth: Boolean; extraSpacingX: Integer = 0; extraSpacingY: Integer = 0; packSyms: Boolean = True): Boolean;
    function Save(fileName: String): Boolean;
    function Load(name, fileName: String; smooth: Boolean): Boolean;
    procedure Draw(text: String; x, y: Single; color: TClr);
    function Width(text: String): Single;
  end;

implementation

uses
{$IFDEF WIN32}
  Windows,
{$ENDIF}
  uVar;

function TFnt.Gen(name, fileName: String; height, weight: Integer; face: String; lang, texW, texH: Integer; smooth: Boolean; extraSpacingX: Integer = 0; extraSpacingY: Integer = 0; packSyms: Boolean = True): Boolean;
{$IFDEF WIN32}
var
  fntHandle, charset: Cardinal;
  tm: TTextMetric;
  gm: TGlyphMetrics;
  widths: array[0..255] of TABC;
  mat: TMat2;
  dc: Cardinal;
  bitmap: Cardinal;
  rect: TRect;
  x, y, i: Integer;
	c: Char;
  info: TBitmapInfo;
  data: TBytes;
{$ENDIF}
begin
  Result := False;

{$IFDEF WIN32}
  case lang of
    FNT_EN: charset := ANSI_CHARSET;
    FNT_RU: charset := RUSSIAN_CHARSET;
    else charset := ANSI_CHARSET;
  end;

  fntHandle := CreateFont(height, 0, 0, 0, weight, 0, 0, 0, charset,
    OUT_TT_ONLY_PRECIS, 0, ANTIALIASED_QUALITY, 0, PChar(face));

  if fntHandle = 0 then
  begin
    //error
    Exit;
  end;

  dc := CreateCompatibleDC(0);
  bitmap := CreateCompatibleBitmap(dc, texW, texH);
  
  SelectObject(dc, bitmap);
  SetRect(rect, 0, 0, texW, texH);
  FillRect(dc, rect, GetStockObject(BLACK_BRUSH));

  SelectObject(dc, fntHandle);
  SetBkMode(dc, TRANSPARENT);
  SetTextColor(dc, $FFFFFF);

  GetTextMetrics(dc, tm);
  GetCharABCWidths(dc, 0, 255, widths);

  mat.eM11.fract := 0; mat.eM11.value := 1;
  mat.eM12.fract := 0; mat.eM12.value := 0;
  mat.eM21.fract := 0; mat.eM21.value := 0;
  mat.eM22.fract := 0; mat.eM22.value := 1;

  x := extraSpacingX;
  y := extraSpacingY;

  for i := 0 to 255 do
  begin
    if (x + tm.tmMaxCharWidth + extraSpacingX) > texW then
    begin
      x := extraSpacingX;
      y := y + height + extraSpacingY * 2;
    end;

		c := Char(i);
		
    TextOut(dc, x, y, @c, 1);
    GetGlyphOutline(dc, i, GGO_METRICS, gm, 0, nil, mat);

    syms[i].advance := widths[i].abcA + Integer(widths[i].abcB) + widths[i].abcC;

    if widths[i].abcA < 0 then Inc(syms[i].advance, -widths[i].abcA);
    if widths[i].abcC < 0 then Inc(syms[i].advance, -widths[i].abcC);

    if packSyms then
    begin
      syms[i].x := gm.gmptGlyphOrigin.X;
      syms[i].y := tm.tmAscent - gm.gmptGlyphOrigin.Y;
      syms[i].w := gm.gmBlackBoxX;
      syms[i].h := gm.gmBlackBoxY;
    end else
    begin
      syms[i].x := -extraSpacingX;
      syms[i].y := -extraSpacingY;
      syms[i].w := syms[i].advance + extraSpacingX * 2;
      syms[i].h := height + extraSpacingY * 2;
    end;

    syms[i].coords[0].x := (x + syms[i].x) / texW;
    syms[i].coords[0].y := (y + syms[i].y) / texH;
    syms[i].coords[1].x := (x + syms[i].x + syms[i].w) / texW;
    syms[i].coords[1].y := (y + syms[i].y + syms[i].h) / texH;

    x := x + syms[i].advance + extraSpacingX * 2;
  end;

  FillChar(info, SizeOf(info), 0);

  with info.bmiHeader do
  begin
    biSize := SizeOf(TBitmapInfoHeader);
    biWidth := texW;
    biHeight := texH;
    biPlanes := 1;
    biBitCount := 24;
    biSizeImage := biWidth * biHeight * 3;
  end;

  SetLength(data, texW * texH * 3);
  GetDIBits(dc, bitmap, 0, texH, data, info, DIB_RGB_COLORS);
  DeleteObject(bitmap);
  DeleteDC(dc);

  for i := 0 to texW * texH - 1 do
    data[i] := data[i * 3];

  FlipV(data, texW, texH, TEX_ALPHA);

  Self.name := name;
  Self.fileName := fileName;
  Self.tex := res.AddTex(name + '_tex', name + '_tex', texW, texH, TEX_ALPHA, data, smooth);
  Self.height := height;
  Self.lineSkip := tm.tmExternalLeading;
  Self.spacing := 0;
  Self.scale := 1;
{$ENDIF}

  Result := True;
end;

function TFnt.Save(fileName: String): Boolean;
var
  f: TBinFile;
begin
  Result := False;

  if not tex.Save(MakePath(fileName)) then
  begin
    //error
    Exit;
  end;

  f := fs.AddBin('', fileName + '.dat', FS_WRITE);

  if f = nil then Exit;

  f.Write(syms[0], SizeOf(syms));
  f.Write(height, SizeOf(height));
  f.Write(lineSkip, SizeOf(lineSkip));
  f.Write(spacing, SizeOf(spacing));
  f.Write(scale, SizeOf(scale));

  fs.Rem(f);

  Result := True;
end;

function TFnt.Load(name, fileName: String; smooth: Boolean): Boolean;
var
  f: TBinFile;
begin
  Result := False;

  tex := res.AddTex(name, fileName, smooth);

  if tex = nil then
  begin
    //error
    Exit;
  end;

  f := fs.AddBin(name, fileName + '.dat', FS_READ);

  if f = nil then Exit;

  f.Read(syms[0], SizeOf(syms));
  f.Read(height, SizeOf(height));
  f.Read(lineSkip, SizeOf(lineSkip));
  f.Read(spacing, SizeOf(spacing));
  f.Read(scale, SizeOf(scale));
  
  fs.Rem(f);

  Self.name := name;
  Self.fileName := fileName;

  Result := True;
end;

procedure TFnt.Draw(text: String; x, y: Single; color: TClr);
var
  i: Integer;
  s: ^TFntSym;
  v: array[0..3] of TGfxVert;
begin
  gfx.PushTrans;
  gfx.Trans(x, y);
  gfx.Scale(scale, scale);

  if tex <> nil then
    tex.Bind;

  x := 0;
  y := 0;

  for i := 1 to Length(text) do
  begin
    if text[i] = #13 then
    begin
      x := 0;
      y := y + height + lineSkip;
      continue;
    end;

    s := @syms[Ord(text[i])];

    v[0] := MakeGfxVert(x + s.x,        y + s.y,        s.coords[0].x, s.coords[0].y, color);
    v[1] := MakeGfxVert(x + s.x + s.w,  y + s.y,        s.coords[1].x, s.coords[0].y, color);
    v[2] := MakeGfxVert(x + s.x + s.w,  y + s.y + s.h,  s.coords[1].x, s.coords[1].y, color);
    v[3] := MakeGfxVert(x + s.x,        y + s.y + s.h,  s.coords[0].x, s.coords[1].y, color);

    gfx.DrawPrim(GFX_TRIFAN, 2, @v[0]);

    x := x + s.advance + spacing;
  end;
  
  gfx.PopTrans;
end;

function TFnt.Width(text: String): Single;
var
  i: Integer;
begin
  Result := 0;

  for i := 1 to Length(text) do
    Result := Result + syms[Ord(text[i])].advance;

  Result := Result * scale;
end;

end.

